/* s2 - benchmark 'find first zero byte'
   Copyright (C) 2018 Ariadne Devos

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include <sHT/bitops.h>
#include <sHT/compiler.h>
#include <sHT/nospec.h>

#include <stdint.h>
#include <time.h>
#include <endian.h>

#include <stdio.h>

#ifdef bench_clz
# include "clz.c"
#elif defined(bench_unroll8)
# include "unroll8.c"
#elif defined(bench_loop)
# include "loop.c"
#endif


/* To stop the compiler from reordering. Might be unnecessary.*/
static uint64_t depends;
#define N_TESTS 1000000

/* Python: for x in range(32): print(math.floor(64 * random.random()))

   (Pseudo-)random values should be closer to what one can expect in a
   real-world and even real-time situations than some arbitrary
   multiplications.

   Values are from 0 up to 2**5 - 1 = 63. */
static const uint8_t sHT_uniform5[] = {
	62, 15, 16, 29, 22, 43, 29, 10,
	6, 5, 58, 43, 59, 0, 20, 18,
	38, 60, 47, 6, 20, 52, 0, 44,
	50, 12, 50, 43, 43, 50, 52, 29,
};

/* Test results:

   for x in clz unroll8 loop; do
     echo "$x";
     gcc -Os "-Dbench_$x" bench.c -I../.. -o "bench-$x";
     nm -S -td "bench-$x" | grep -E 'sHT_word_zero_index64$' | cut -d' ' -f'2';
     "./bench-$x";
   done */

/* Overestimate the time to peform an operation,
   after bringing the CPU up to speed. */
int
main(int argc, char **argv)
{
	struct timespec before, after;
	int ret;
	depends = 0;
	for (long int i = 0; sHT_test_hidden(i, i < N_TESTS * 32); i++) {
		/* Bring the processor up to speed */
		int val = sHT_word_zero_index64(sHT_uniform5[i & 63]);
		sHT_depend(depends, val);
	}
	clockid_t c = CLOCK_MONOTONIC;
	sHT_depend(c, depends);
	ret = clock_gettime(CLOCK_MONOTONIC, &before);
	sHT_depend(depends, ret);
	if (sHT_unlikely(sHT_test_hidden(ret, ret)))
		return 2;
	for (long int i = 0; sHT_test_hidden(i, i < N_TESTS); i++) {
		/* The index may be incorrect, because it isn't really
		   an index -- only a loop counter. */
		i = sHT_index_nospec(i, N_TESTS);
		val = sHT_word_zero_index64(1 << sHT_uniform5[i & 63]);
		sHT_depend(depends, val);
		val = sHT_word_zero_index64(1 << sHT_uniform5[(j + 1) & 63]);
		sHT_depend(depends, val);
		val = sHT_word_zero_index64(1 << sHT_uniform5[(j + 2) & 63]);
		sHT_depend(depends, val);
		val = sHT_word_zero_index64(1 << sHT_uniform5[(j + 3) & 63]);
		sHT_depend(depends, val);
	}
	ret = clock_gettime(CLOCK_MONOTONIC, &after);
	sHT_depend(ret, depends);
	/* Compute after - before */
	uint64_t diff_nano = ((uint64_t) (after.tv_sec - before.tv_sec) * 1000000000) + (after.tv_nsec - before.tv_nsec);
	uint64_t period_high = (diff_nano + 3 * N_TESTS)/(4 * N_TESTS);
	uint64_t period_low = ((diff_nano + 3 * N_TESTS)/(4 * N_TESTS/100)) % 100;
	printf("%llu ns / %llu = %llu.%llu ns\n", diff_nano, (uint64_t) N_TESTS, period_high, period_low);
	return 0;
}
